<?php
/**
 * フォームバリデータクラス定義ファイル
 *
 * フォームバリデータ本体と基本プラグインが含まれています
 */

/**
 * フォームバリデータクラス
 *
 */
class FormValidator {
	/**
	 * ルールを格納
	 *
	 * @var SimpleXMLIterator
	 */
	private $rule = null;

	/**
	 * コンストラクタ
	 *
	 * @param string $fileName ルールファイル名
	 */
	function __construct($fileName) {
		$path = VISION_APP_PATH."settings/$fileName";
		$this->rule = simplexml_load_file($path, 'SimpleXMLElement');
	}

	/**
	 * 妥当性検査を開始
	 *
	 * @param SimpleXMLElement $reqElem Requestクラスが生成したバリューオブジェクトの要素
	 * @param SimpleXMLElement $resultElem 検証統計を保存するバリューオブジェクトの要素
	 * @param string $formName 対象フォームの名前
	 * @return integer エラー発生総数
	 */
	public function execute($reqElem, $resultElem, $formName, $skipFlag) {
		// 設定ファイルから対象フォームの検査対象フィールドを読み出す
		$fields = $this->rule->$formName->field;
		// 存在しないフィールドの数
		$skip_field = 0;
		// エラー総数
		$errCount = 0;
		// 検査対象の数だけループ
		foreach ($fields as $field) {
			// フィールド名を保存
			$fieldName = $field['name'];
			// 必須フィールドを先にチェックし、存在しない場合はダミーvalueを作る
			if (($field['require'] == 'true') && ($skipFlag != true)) {
				if (!$reqElem->$fieldName) {
					$reqElem->addChild($fieldName)->addChild('value','');
				}
			}
			// 検査対象フィールドが、リクエスト要素に存在する？
			if ($reqElem->$fieldName) {
				// 各検査項目の数だけループ
				foreach ($field as $checkPoint) {
					// 検査項目ごとにプラグインを呼び出す
					$FVPluginClassName = 'FVPlugin_'.(string) $checkPoint->getName();
					class_exists($FVPluginClassName);
					$FVPlugin = new $FVPluginClassName;
					$errCount += $FVPlugin->validation($checkPoint, $reqElem, $fieldName);
				}
			} else {
				// 検査対象が存在しない場合は設定によって例外を発生させる
				if (Vision::$setting->formvalidator->skip_undefined_field != 'true') {
					$validatorErrMsg = "FormValidatorエラー：<br />\n";
					$validatorErrMsg .= "検査対象フィールド[$fieldName]が見つかりません.<br />\n";
					throw new Exception($validatorErrMsg);
				}
				$skip_field++;
			}
		}
		if (($errCount > 0) || ($skip_field > 0)) {
			// XPathにてエラーメッセージを集合化
			$errMsgList = $reqElem->xpath('//error/msg');
			if (is_array($errMsgList)) {
				foreach ($errMsgList as $errMsgStr) {
					$resultElem->addChild('errmsg', $errMsgStr);
				}
				// 最初に起こったエラー
				$resultElem->addChild('firsterrmsg', reset($errMsgList));
				// 最後に起こったエラー
				$resultElem->addChild('lasterrmsg', end($errMsgList));

			}
			// 検証エラーの数
			$resultElem->addChild('errcount', $errCount);
			// スキップした項目の数
			$resultElem->addChild('skipfield', $skip_field);
			return $errCount + $skip_field;
		} else {
			return 0;
		}
	}
}

/**
 * フォームバリデータプラグイン基底クラス
 *
 * すべてのフォームバリデータプラグインはこのクラスを継承しなければならない
 */
abstract class FVPlugin_base {
	/**
	 * エラー情報を格納する要素を取得または作成する
	 *
	 * @param SimpleXMLElement $targetElem エラー情報を格納したい要素
	 * @return SimpleXMLElement 取得または作成された	エラー情報の要素
	 */
	protected function getErrElem(SimpleXMLElement $targetElem) {
		if ($targetElem->error) {
			return $targetElem->error;
		} else {
			return $targetElem->addChild('error');
		}
	}
	/**
	 * エラーメッセージを格納する
	 *
	 * @param SimpleXMLElement $errorElem エラー情報の要素
	 * @param string $sender エラーの発信元
	 * @param SimpleXMLElement $errMsg エラーメッセージが格納されている要素
	 */
	protected function setErrMsg(SimpleXMLElement $errorElem, $sender, SimpleXMLElement $errMsg) {
		if (!is_null($errMsg['msg'])) {
			$errMsgElem = $errorElem->addChild('msg', (string) $errMsg['msg']);
			$errMsgElem->addAttribute('sender', $sender);
		}
	}
	/**
	 * エラー数をカウントアップする
	 *
	 * @param SimpleXMLElement $targetElem 処理対象のerror要素
	 */
	protected function errCountUp(SimpleXMLElement $errorElem) {
		if (is_null($errorElem['count'])) {
			$errorElem->addAttribute('count', '1');
		} else {
			$errorElem['count'] = (((integer) $errorElem['count']) + 1);
		}
	}
	/**
	 * 妥当性検査を開始
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	abstract function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName);
}

/**
 * 必須入力データのチェック
 *
 */
class FVPlugin_require extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		$errs = 0;
		foreach ($target->$fieldName as $targetElem) {
			if (strlen($targetElem->value) == 0) {
				$errorElem = $this->getErrElem($targetElem);
				$errMsgElem = $this->setErrMsg($errorElem, 'required', $param);
				$this->errCountUp($errorElem);
				$errs++;
			}
		}
		return $errs;
	}
}

/**
 * 入力データの正規表現チェック
 *
 */
class FVPlugin_match extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		$errs = 0;
		foreach ($target->$fieldName as $targetElem) {
			if (!mb_ereg(trim($param['pattern']), $targetElem->value)) {
				$errorElem = $this->getErrElem($targetElem);
				$errMsgElem = $this->setErrMsg($errorElem, 'match', $param);
				$this->errCountUp($errorElem);
				$errs++;
			}
		}
		return $errs;
	}
}

/**
 * 入力データの変形
 *
 * メソッドとパラメータによって様々なデータ変形を行う
 * 		trim - 前後の空白を除去する<br>
 * 		convert_kana - mb_convert_kana関数にて全角・半角等の相互変換を行う<br>
 * 			param1 mb_convert_kanaに渡される変換オプション<br>
 * 		convert_case - ケールフォールディングを行う<br>
 * 			param1 lower=小文字に変換,upper=大文字に変換<br>
 *
 */
class FVPlugin_transform extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		foreach ($target->$fieldName as $targetElem) {
			switch ((string) $param['method']) {
				case 'trim':
					$targetElem->value = trim(htmlspecialchars($targetElem->value, ENT_QUOTES, 'UTF-8'));
					break;
				case 'convert_kana':
					$targetElem->value = mb_convert_kana((string) $targetElem->value, (string) $param['param1']);
					break;
				case 'convert_case':
					switch((string) $param['param1']) {
						case 'lower':
							$targetElem->value = mb_strtolower($targetElem->value);
							break;
						case 'upper':
							$targetElem->value = mb_strtoupper($targetElem->value);
							break;
						default:
					}
					break;
				case 'ereg_replace':
					$targetElem->value = mb_ereg_replace((string) $param['param1'], 
					                                     (string) $param['param2'],(string) $targetElem->value);
					break;
			}
		}
		return 0;
	}
}

/**
 * 入力データの文字長をチェックする
 *
 */
class FVPlugin_length extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		$errs = 0;
		foreach ($target->$fieldName as $targetElem) {
			foreach ($param as $paramElem) {
				switch ($paramElem->getName()) {
					case 'min':
						if (mb_strlen($targetElem->value) < (integer) $paramElem) {
							$errorElem = $this->getErrElem($targetElem);
							$errMsgElem = $this->setErrMsg($errorElem, 'min', $paramElem);
							$this->errCountUp($errorElem);
							$errs++;
						}
						break;
					case 'max':
						if (mb_strlen($targetElem->value) > (integer) $paramElem) {
							$errorElem = $this->getErrElem($targetElem);
							$errMsgElem = $this->setErrMsg($errorElem, 'max', $paramElem);
							$this->errCountUp($errorElem);
							$errs++;
						}
						break;
				}
			}
		}
		return $errs;
	}
}

/**
 * デフォルトのエラーメッセージを指定する
 *
 * エラーが発生していなければメッセージは追加出来ないので
 * 検査項目の最後に付加するべきです
 *
 */
class FVPlugin_default extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		foreach ($target->$fieldName as $targetElem) {
			if ($targetElem->error) {
				$errorElem = $this->getErrElem($targetElem);
				$errMsgElem = $this->setErrMsg($errorElem, 'default', $param);
			}
		}
	}
}

/**
 * スタイルシートを選択する
 *
 * エラーが発生している場合、していない場合でCSSを変えたい場合
 * このプラグインを使って下さい
 *
 */
class FVPlugin_css extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		foreach ($target->$fieldName as $targetElem) {
			$css_str = '';
			if ($targetElem->error) {
				if (!is_null($param['err'])) {
					$css_str = (string) $param['err'];
				}
			} else {
				if (!is_null($param['def'])) {
					$css_str = (string) $param['def'];
				}
			}
			if ($css_str != '') {
				$targetElem->addChild('css', $css_str);
			}
		}
	}
}

/**
 * エラー時に入力データを消去する
 *
 */
class FVPlugin_erase extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		foreach ($target->$fieldName as $targetElem) {
			if ($targetElem->error) {
				$targetElem->value = '';
			}
		}
	}
}

/**
 * ２つの入力フィールドを比較する
 *
 * 電子メールアドレスの確認、パスワードの確認に利用できます
 *
 */
class FVPlugin_equal extends FVPlugin_base {
	/**
	 * プラグイン動作
	 *
	 * @param SimpleXMLElement $param 検査に必要なパラメータが格納されている要素
	 * @param SimpleXMLElement $target 検査対象要素
	 * @param string 検査対象フィールド名
	 * @return integer エラー個数
	 */
	public function validation(SimpleXMLElement $param, SimpleXMLElement $target, $fieldName) {
		$errs = 0;
		$check1 = (string) $target->$param['field']->value;
		$check2 = (string) $target->$fieldName->value;
		if ($check1 != $check2) {
			$errorElem = $this->getErrElem($target->$fieldName);
			$errMsgElem = $this->setErrMsg($errorElem, 'equal', $param);
			$this->errCountUp($errorElem);
			$errs++;
		}
		return $errs;
	}
}
